package pl.edu.pb.wi.pwnography.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import pl.edu.pb.wi.pwnography.model.form.AssessmentQualityAllForm;
import pl.edu.pb.wi.pwnography.model.form.AssessmentQualityForm;
import pl.edu.pb.wi.pwnography.model.form.ClassificationAllForm;
import pl.edu.pb.wi.pwnography.model.form.ClassificationForm;
import pl.edu.pb.wi.pwnography.modules.KNNAllAttributes;
import pl.edu.pb.wi.pwnography.modules.KNNTwoAttributes;
import pl.edu.pb.wi.pwnography.modules.KNearestNeighbor;
import pl.edu.pb.wi.pwnography.session.FileList;

@Controller
@RequestMapping("/module/knn")
public class KNearestNeighborModule {
    private static final Logger log = Logger
	    .getLogger(KNearestNeighborModule.class.getName());

    private static final String CLASSIFICATION_ALL_ATTRIBUTE_VALUES_ERROR = "Pole nie zostało prawidłowo wypełnione.";
    private static final String CLASSIFICATION_ALL_DESCRIPTION = "Obiekt został przypisany do klasy '%s' bazując na metryce %s.";
    private static final String CLASSIFICATION_ALL_ERROR = "Wystąpił błąd w trakcie klasyfikacji obiektu.";
    private static final String CLASSIFICATION_DESCRIPTION = "Obiekt o atrybutach <code>a: %f</code> oraz <code>b: %f</code> został przypisany do klasy '%s' bazując na metryce %s.";
    private static final String CLASSIFICATION_ERROR = "Wystąpił błąd w trakcie klasyfikacji obiektu <code>a: %f</code> oraz <code>b: %f</code>.";
    private static final String ASSESSMENT_QUALITY_DESCRIPTION = "Ocena jakości klasyfikacji K-NN metodą 'leave-one-out' oraz metryką odległości %s dla komumn: '%s', '%s' oraz klasy decyzyjnej '%s'. Wynik: %f W procentach: %f%%";
    private static final String ASSESSMENT_QUALITY_ALL_DESCRIPTION = "Ocena jakości klasyfikacji K-NN metodą 'leave-one-out', metryką odległości %s dla klasy decyzyjnej '%s'. Wynik: %f W procentach: %f%%";
    private static final String ASSESSMENT_QUALITY_ERROR = "Wystąpił błąd w trakcie oceny jakości klasyfikacji.";

    @Autowired
    private FileList fileList;

    public void setFileList(FileList fileList) {
	this.fileList = fileList;
    }

    @ModelAttribute("floatColumnList")
    public List<String> getFloatColumnNames() {
	return fileList.getActiveFile().getFloatTypeColumnNames();
    }

    @ModelAttribute("stringColumnList")
    public List<String> getStringColumnNames() {
	return fileList.getActiveFile().getColumnNames();
    }

    @ModelAttribute("metricList")
    public List<KNearestNeighbor.METRIC> getMetricList() {
	return Arrays.asList(KNearestNeighbor.METRIC.values());
    }

    @RequestMapping(value = "assessmentquality", method = RequestMethod.GET)
    public String assessmentQuality(Model model) {
	log.info("Assessment quality GET.");

	model.addAttribute("form", new AssessmentQualityForm());

	return "module/knn/assessmentquality";
    }

    @RequestMapping(value = "assessmentquality", method = RequestMethod.POST)
    public String assessmentQuality(
	    @ModelAttribute("form") @Valid AssessmentQualityForm form,
	    BindingResult results, Model model) {
	log.info("Assessment quality POST.");

	if (results.hasErrors()) {
	    return "module/knn/assessmentquality";
	}

	try {
	    List<Object> newClasses = KNNTwoAttributes.leaveOneOut(
		    fileList.getActiveFile().getColumnValues(
			    form.getColumnName()),
		    fileList.getActiveFile().getColumnValues(
			    form.getSecondColumn()), fileList.getActiveFile()
			    .getColumnValues(form.getClassColumn()), form
			    .getKneighbors(), form.getMetric());

	    float assessmentQuality = KNNTwoAttributes.assessmentQuality(
		    fileList.getActiveFile().getColumnValues(
			    form.getClassColumn()), newClasses);

	    String description = String.format(ASSESSMENT_QUALITY_DESCRIPTION,
		    form.getMetric(), form.getColumnName(),
		    form.getSecondColumn(), form.getClassColumn(),
		    assessmentQuality, assessmentQuality * 100);

	    if (form.getSaveToFile()) {
		Map<String, List<Object>> data;
		if (form.getResultColumnName() == null
			|| form.getResultColumnName().isEmpty())
		    data = KNNTwoAttributes.createColumn(form.getColumnName(),
			    form.getSecondColumn(), form.getClassColumn(),
			    newClasses, form.getMetric());
		else {
		    data = new LinkedHashMap<String, List<Object>>();
		    data.put(form.getResultColumnName(), newClasses);
		}

		fileList.getActiveFile().addNewRevision(data, description);
		log.info(fileList.getActiveFile().getCurrentRevisionData()
			.toString());
	    }

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { description }));
	} catch (Exception e) {
	    log.info(String.format(
		    "Problem with K-NN asessment quality . Exception: %s",
		    e.getMessage()));

	    model.addAttribute(
		    "errorMsgs",
		    Arrays.asList(new String[] { ASSESSMENT_QUALITY_ERROR,
			    e.getMessage() }));
	}

	return "module/knn/assessmentquality";
    }

    @RequestMapping(value = "classification", method = RequestMethod.GET)
    public String classification(Model model) {
	log.info("Classification GET.");

	model.addAttribute("form", new ClassificationForm());

	return "module/knn/classification";
    }

    @RequestMapping(value = "classification", method = RequestMethod.POST)
    public String classification(
	    @ModelAttribute("form") @Valid ClassificationForm form,
	    BindingResult results, Model model) {
	log.info("Classification POST.");

	if (results.hasErrors()) {
	    return "module/knn/classification";
	}

	try {
	    Object classificationClass = KNNTwoAttributes.classification(
		    fileList.getActiveFile().getColumnValues(
			    form.getColumnName()),
		    fileList.getActiveFile().getColumnValues(
			    form.getSecondColumn()),
		    fileList.getActiveFile().getColumnValues(
			    form.getClassColumn()), form.getKneighbors(), form
			    .getMetric(), form.getFirstAttributeValue(), form
			    .getSecondAttributeValue());

	    String description = String.format(CLASSIFICATION_DESCRIPTION,
		    form.getFirstAttributeValue(),
		    form.getSecondAttributeValue(),
		    classificationClass.toString(), form.getMetric());

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { description }));
	} catch (Exception e) {
	    log.info(String.format(
		    "Problem with K-NN classification. Exception: %s",
		    e.getMessage()));

	    model.addAttribute(
		    "errorMsgs",
		    Arrays.asList(new String[] {
			    String.format(CLASSIFICATION_ERROR,
				    form.getFirstAttributeValue(),
				    form.getSecondAttributeValue()),
			    e.getMessage() }));
	}

	return "module/knn/classification";
    }

    @RequestMapping(value = "all/assessmentquality", method = RequestMethod.GET)
    public String assessmentQualityAll(Model model) {
	log.info("AssessmentAll quality GET.");

	model.addAttribute("form", new AssessmentQualityAllForm());

	return "module/knn/all/assessmentquality";
    }

    @RequestMapping(value = "all/assessmentquality", method = RequestMethod.POST)
    public String assessmentQualityAll(
	    @ModelAttribute("form") @Valid AssessmentQualityAllForm form,
	    BindingResult results, Model model) {
	log.info("AssessmentAll quality POST.");

	if (results.hasErrors()) {
	    return "module/knn/all/assessmentquality";
	}

	try {
	    List<Object> newClasses = KNNAllAttributes.leaveOneOut(
		    fileList.getActiveFile(), form.getExcludedColumns(),
		    form.getColumnName(), form.getKneighbors(),
		    form.getMetric(), fileList.getActiveFile().getRowCount(),
		    fileList.getActiveFile().getData());

	    float assessmentQuality = KNNAllAttributes.assessmentQuality(
		    fileList.getActiveFile().getColumnValues(
			    form.getColumnName()), newClasses);

	    String description = String.format(
		    ASSESSMENT_QUALITY_ALL_DESCRIPTION, form.getMetric(),
		    form.getColumnName(), assessmentQuality,
		    assessmentQuality * 100);

	    if (form.getSaveToFile()) {
		Map<String, List<Object>> data;
		if (form.getResultColumnName() == null
			|| form.getResultColumnName().isEmpty())
		    data = KNNAllAttributes.createColumn(form.getColumnName(),
			    newClasses, form.getMetric(), form.getKneighbors());
		else {
		    data = new LinkedHashMap<String, List<Object>>();
		    data.put(form.getResultColumnName(), newClasses);
		}

		fileList.getActiveFile().addNewRevision(data, description);
		log.info(fileList.getActiveFile().getCurrentRevisionData()
			.toString());
	    }

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { description }));
	} catch (Exception e) {
	    log.info(String.format(
		    "Problem with K-NN asessment all quality . Exception: %s",
		    e.getMessage()));

	    model.addAttribute(
		    "errorMsgs",
		    Arrays.asList(new String[] { ASSESSMENT_QUALITY_ERROR,
			    e.getMessage() }));
	}

	return "module/knn/all/assessmentquality";
    }

    @RequestMapping(value = "all/classification", method = RequestMethod.GET)
    public String classificationAll(Model model) {
	log.info("Classification all GET.");

	model.addAttribute("form", new ClassificationAllForm());

	return "module/knn/all/classification";
    }

    @RequestMapping(value = "all/classification", method = RequestMethod.POST)
    public String classificationAll(
	    @ModelAttribute("form") @Valid ClassificationAllForm form,
	    BindingResult results, Model model) {
	log.info("Classification all POST.");

	List<Float> parsedValues = new ArrayList<Float>();
	try {
	    String[] stringValues = form.getAttributeValues().trim().split(",");
	    for (String s : stringValues)
		parsedValues.add(Float.valueOf(s.trim()));
	} catch (Exception ex) {
	    results.addError(new FieldError("form", "attributeValues",
		    CLASSIFICATION_ALL_ATTRIBUTE_VALUES_ERROR));
	}

	if (results.hasErrors()) {
	    return "module/knn/all/classification";
	}

	try {
	    Object classificationClass = KNNAllAttributes.classification(
		    fileList.getActiveFile(), form.getExcludedColumns(),
		    form.getColumnName(), parsedValues, form.getKneighbors(),
		    form.getMetric());

	    String description = String.format(CLASSIFICATION_ALL_DESCRIPTION,
		    classificationClass.toString(), form.getMetric());

	    model.addAttribute("successMsgs",
		    Arrays.asList(new String[] { description }));
	} catch (Exception e) {
	    log.info(String.format(
		    "Problem with K-NN classification all. Exception: %s",
		    e.getMessage()));

	    model.addAttribute(
		    "errorMsgs",
		    Arrays.asList(new String[] { CLASSIFICATION_ALL_ERROR,
			    e.getMessage() }));
	}

	return "module/knn/all/classification";
    }
}
